<template>
  <div v-if="easyFlowVisible" style="height: calc(100vh);">
    <div style="display: flex;height: calc(100% - 47px);">
      <div style="width: 230px;border-right: 1px solid #dce3e8;">
        <node-menu @addNode="addNode" ref="nodeMenu"></node-menu>
      </div>
      <!-- 画布 -->
      <div id="efContainer" ref="efContainer" class="container" v-flowDrag>
        <div class="ef-tooltar">
          <el-link type="primary" :underline="false">{{data.name}}</el-link>
          <!-- <el-divider direction="vertical"></el-divider>
                    <el-button type="text" icon="el-icon-delete" size="large" @click="deleteElement"
                        :disabled="!this.activeElement.type"></el-button>
          <el-divider direction="vertical"></el-divider>-->
          <el-button type="text" icon="el-icon-download" size="large" @click="downloadData"></el-button>
          <div style="float: right;margin-right: 5px">
            <el-button plain round icon="el-icon-document" @click="dataInfo" size="mini">流程信息</el-button>
          </div>
        </div>
        <template v-for="node in data.nodeList">
          <flow-node
            :id="node.id"
            :key="node.id"
            :node="node"
            :activeElement="activeElement"
            :tid="node.type"
            @changeNodeSite="changeNodeSite"
            @clickNode="clickNode"
            @deleteNode="deleteNode"
          ></flow-node>
        </template>

        <div style="position:absolute;">&nbsp;</div>
      </div>
      <!-- 右侧表单 -->
      <div style="width: 300px;border-left: 1px solid #dce3e8;background-color: #FBFBFB">
        <!-- <flow-node-form ref="nodeForm" @setLineLabel="setLineLabel"></flow-node-form> -->

        <div class="ef-node-form">
          <div class="ef-node-form-header">设置</div>
          <div class="ef-node-form-body">
            <el-form :model="form" ref="dataForm" label-width="80px">
              <el-form-item label="作业名称">
                <el-input v-model="project.name"></el-input>
              </el-form-item>
              <el-form-item label="告警通知人">
                <el-input v-model="project.tzr"></el-input>
              </el-form-item>

              <el-form-item>
                <el-button type="primary" icon="el-icon-check" @click="save">保存</el-button>
                <el-button icon="el-icon-close">重置</el-button>
              </el-form-item>
            </el-form>
          </div>
          <div class="el-node-form-tag"></div>
        </div>
      </div>
    </div>
    <!-- 流程数据详情 -->
    <flow-info v-if="flowInfoVisible" ref="flowInfo" :data="data"></flow-info>
  </div>
</template>

<script>
import draggable from "vuedraggable";
// import { jsPlumb } from 'jsplumb'
// 使用修改后的jsplumb
import "./jsplumb";
import { easyFlowMixin } from "@/components/ef/mixins";
import flowNode from "@/components/ef/node";
import nodeMenu from "@/components/ef/node_menu";
import FlowInfo from "@/components/ef/info";

import lodash from "lodash";
// import { getDataA } from './data_A'
// import { getDataB } from './data_B'
import { getDataC } from "./data_C";

export default {
  data() {
    return {
      //表单
      form: {},
      //表单相关
      project: {},
      // jsPlumb 实例
      jsPlumb: null,
      // 控制画布销毁
      easyFlowVisible: true,
      // 控制流程数据显示与隐藏
      flowInfoVisible: false,
      // 是否加载完毕标志位
      loadEasyFlowFinish: false,
      // 数据
      data: {},
      // 激活的元素、可能是节点、可能是连线
      activeElement: {
        // 可选值 node 、line
        type: undefined,
        // 节点ID
        nodeId: undefined,
        // 连线ID
        sourceId: undefined,
        targetId: undefined
      },
      //以下是拓扑排序相关
      nodes: [],
      list: {}, // 邻接表
      queue: [], // 入度为0的节点集合
      indegree: {},
      v: null
    };
  },
  // 一些基础配置移动该文件中
  mixins: [easyFlowMixin],
  components: {
    draggable,
    flowNode,
    nodeMenu,
    FlowInfo
  },
  directives: {
    flowDrag: {
      bind(el, binding, vnode, oldNode) {
        if (!binding) {
          return;
        }
        el.onmousedown = e => {
          // todo 取消激活的元素
          // e.activeElement.type = undefined
          // e.activeElement.nodeId = undefined
          if (e.button == 2) {
            // 右键不管
            return;
          }
          //  鼠标按下，计算当前原始距离可视区的高度
          let disX = e.clientX;
          let disY = e.clientY;
          el.style.cursor = "move";

          document.onmousemove = function(e) {
            // 移动时禁止默认事件
            e.preventDefault();
            const left = e.clientX - disX;
            disX = e.clientX;
            el.scrollLeft += -left;

            const top = e.clientY - disY;
            disY = e.clientY;
            el.scrollTop += -top;
          };

          document.onmouseup = function(e) {
            el.style.cursor = "auto";
            document.onmousemove = null;
            document.onmouseup = null;
          };
        };
      }
    }
  },
  mounted() {
    this.jsPlumb = jsPlumb.getInstance();
    this.$nextTick(() => {
      // 默认的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可
      this.dataReload(getDataC());
    });
  },
  methods: {
    //点击保存按钮
    save() {
      if (this.data.nodeList.length <= 2) {
        this.$message.error("请添加任务（需包含开始和结束）！");
        return;
      }

      if (this.data.lineList.length < 2) {
        this.$message.error("连线不完整！");
        return;
      }
      var start = 0;
      var end = 0;
      this.data.nodeList.forEach(element => {
        if (element.type === "start") {
          start = 1;
        }
        if (element.type === "end") {
          end = 1;
        }
      });

      //  至少要有一个开始节点
      if (start === 0) {
        this.$message.error("还未添加开始节点！");
        return;
      }
      //  至少要有一个结束节点
      if (end === 0) {
        this.$message.error("还未添加结束节点！");
        return;
      }

      var startLine = 0;
      var endLine = 0;
      this.data.lineList.forEach(element => {
        if (element.fromId === "start") {
          startLine = 1;
        }
        if (element.toId === "end") {
          endLine = 1;
        }
      });

      //开始节点必须有连出的线
      if (startLine === 0) {
        this.$message.error("开始节点未连接！");
        return;
      }
      //结束节点必须有连入的线
      if (endLine === 0) {
        this.$message.error("结束节点未连接！");
        return;
      }
      //判断每个任务节点至少有一个连入和连出的线
      for (var i = 0; i < this.data.nodeList.length; i++) {
        let element = this.data.nodeList[i];
        if (element.type !== "start" && element.type !== "end") {
          let elementFrom = true;
          let elementTo = true;
          for (var b = 0; b < this.data.lineList.length; b++) {
            let line = this.data.lineList[b];
            //有连出的线
            if (element.id === line.from) {
              elementFrom = false;
            }
            //有连入的线
            if (element.id === line.to) {
              elementTo = false;
            }
          }

          if (elementFrom || elementTo) {
            this.$message.error('"' + element.name + '"' + "缺少完整连线");
            return;
          }
        }
      }

      //  判断图是否为有向无环图 拓扑排序 https://segmentfault.com/a/1190000020241908?utm_source=tag-newest
      //初始化
      this.nodes = [];
      this.list = {}; // 邻接表
      this.queue = []; // 入度为0的节点集合
      this.indegree = {};

      this.data.lineList.forEach(e => {
        const { from, to } = e;
        if (!this.nodes.includes(from)) {
          this.nodes.push(from);
        }
        if (!this.nodes.includes(to)) {
          this.nodes.push(to);
        }
        this.addEdge(from, to);
      });
      this.v = this.nodes.length;

      this.nodes.forEach(node => {
        if (!this.indegree[node]) this.indegree[node] = 0;
        if (!this.list[node]) this.list[node] = [];
      });
      if (!this.sort()) {
        this.$message.error("图中有闭合回路请修改！");
        return;
      }

      console.log("连线验证通过了");
    },

    addEdge(source, target) {
      if (!this.list[source]) this.list[source] = [];
      if (!this.indegree[target]) this.indegree[target] = 0;
      this.list[source].push(target);
      this.indegree[target] += 1;
    },
    sort() {
      Object.keys(this.indegree).forEach(id => {
        if (this.indegree[id] === 0) {
          this.queue.push(id);
        }
      });
      let count = 0;
      while (this.queue.length) {
        ++count;
        const currentNode = this.queue.pop();
        const nodeTargets = this.list[currentNode];
        for (let i = 0; i < nodeTargets.length; i++) {
          const target = nodeTargets[i];
          this.indegree[target] -= 1;
          if (this.indegree[target] === 0) {
            this.queue.push(target);
          }
        }
      }
      // false 没有输出全部顶点，有向图中有回路
      return !(count < this.v);
    },
    cancleDisplayIcon() {
      this.activeElement.type = undefined;
      this.activeElement.nodeId = undefined;
    },
    // 返回唯一标识
    getUUID() {
      return Math.random()
        .toString(36)
        .substr(3, 10);
    },
    jsPlumbInit() {
      this.jsPlumb.ready(() => {
        // 导入默认配置
        this.jsPlumb.importDefaults(this.jsplumbSetting);
        // 会使整个jsPlumb立即重绘。
        this.jsPlumb.setSuspendDrawing(false, true);
        // 初始化节点
        this.loadEasyFlow();
        // 单击连接线,连线删除 https://www.cnblogs.com/ysx215/p/7615677.html
        this.jsPlumb.bind("click", (conn, originalEvent) => {
          this.activeElement.type = "line";
          this.activeElement.sourceId = conn.sourceId;
          this.activeElement.targetId = conn.targetId;
          this.$confirm("确定删除所点击的线吗?", "提示", {
            confirmButtonText: "确定",
            cancelButtonText: "取消",
            type: "warning"
          })
            .then(() => {
              this.jsPlumb.deleteConnection(conn);
            })
            .catch(() => {});
        });
        // 连线
        this.jsPlumb.bind("connection", evt => {
          let from = evt.source.id;
          let to = evt.target.id;
          // todo 连线时添加的参数 菜单对应的id
          let fromId = evt.source.getAttribute("tid");
          let toId = evt.target.getAttribute("tid");
          if (this.loadEasyFlowFinish) {
            this.data.lineList.push({
              from: from,
              fromId: fromId,
              to: to,
              toId: toId
            });
          }
        });

        // 删除连线回调
        this.jsPlumb.bind("connectionDetached", evt => {
          this.deleteLine(evt.sourceId, evt.targetId);
        });

        // 改变线的连接节点
        this.jsPlumb.bind("connectionMoved", evt => {
          this.changeLine(evt.originalSourceId, evt.originalTargetId);
        });

        // 连线右击
        this.jsPlumb.bind("contextmenu", evt => {
          // console.log('contextmenu', evt)
        });

        // 连线
        this.jsPlumb.bind("beforeDrop", evt => {
          //console.log(evt);
          let from = evt.sourceId;
          let to = evt.targetId;
          //  连线时添加的参数 菜单对应的id
          let fromId = evt.connection.source.attributes[2].nodeValue;
          let toId = evt.dropEndpoint.element.attributes[2].nodeValue;
          // 连线规则
          if (from === to) {
            this.$message.error("节点不支持连接自己");
            return false;
          }
          if (fromId === toId) {
            this.$message.error("不支持连接同样的元素");
            return false;
          }
          if (toId === "start") {
            this.$message.error("开始节点不支持被连接");
            return false;
          }
          if (fromId === "end") {
            this.$message.error("结束节点不支持此连接");
            return false;
          }
          if (this.hasLine(from, to)) {
            this.$message.error("该关系已存在,不允许重复创建");
            return false;
          }
          if (this.hashOppositeLine(from, to)) {
            this.$message.error("不支持两个节点之间连线回环");
            return false;
          }
          this.$message.success("连接成功");
          return true;
        });
        // beforeDetach
        this.jsPlumb.bind("beforeDetach", evt => {
          //console.log('beforeDetach', evt)
        });
        this.jsPlumb.setContainer(this.$refs.efContainer);
      });
    },
    // 加载流程图
    loadEasyFlow() {
      // 初始化节点
      for (var i = 0; i < this.data.nodeList.length; i++) {
        let node = this.data.nodeList[i];
        // 设置源点，可以拖出线连接其他节点
        this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions);
        // // 设置目标点，其他源点拖出的线可以连接该节点
        this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions);
        this.jsPlumb.draggable(node.id, {
          containment: "parent",
          stop: function(el) {
            //console.log('停止拖拽', el)
          }
        });
      }
      // 初始化连线
      for (var i = 0; i < this.data.lineList.length; i++) {
        let line = this.data.lineList[i];
        this.jsPlumb.connect(
          {
            source: line.from,
            target: line.to,
            label: line.label ? line.label : ""
          },
          this.jsplumbConnectOptions
        );
      }
      this.$nextTick(function() {
        this.loadEasyFlowFinish = true;
      });
    },
    // setLineLabel(from, to, label) {
    //   var conn = this.jsPlumb.getConnections({
    //     source: from,
    //     target: to
    //   })[0];
    //   if (!label || label === "") {
    //     conn.removeClass("flowLabel");
    //     conn.addClass("emptyFlowLabel");
    //   } else {
    //     conn.addClass("flowLabel");
    //   }
    //   conn.setLabel({
    //     label: label
    //   });
    //   this.data.lineList.forEach(function(line) {
    //     if (line.from == from && line.to == to) {
    //       line.label = label;
    //     }
    //   });
    // },

    // 删除激活的元素
    deleteElement() {
      if (this.activeElement.type === "node") {
        this.deleteNode(this.activeElement.nodeId);
      }
    },
    // 删除线
    deleteLine(from, to) {
      this.data.lineList = this.data.lineList.filter(function(line) {
        if (line.from == from && line.to == to) {
          return false;
        }
        return true;
      });
    },
    // 改变连线
    changeLine(oldFrom, oldTo) {
      this.deleteLine(oldFrom, oldTo);
    },
    // 改变节点的位置
    changeNodeSite(data) {
      for (var i = 0; i < this.data.nodeList.length; i++) {
        let node = this.data.nodeList[i];
        if (node.id === data.nodeId) {
          node.left = data.left;
          node.top = data.top;
        }
      }
    },
    /**
     * 拖拽结束后添加新的节点
     * @param evt
     * @param nodeMenu 被添加的节点对象
     * @param mousePosition 鼠标拖拽结束的坐标
     */
    addNode(evt, nodeMenu, mousePosition) {
      var screenX = evt.originalEvent.clientX,
        screenY = evt.originalEvent.clientY;
      let efContainer = this.$refs.efContainer;
      var containerRect = efContainer.getBoundingClientRect();
      var left = screenX,
        top = screenY;
      // 计算是否拖入到容器中
      if (
        left < containerRect.x ||
        left > containerRect.width + containerRect.x ||
        top < containerRect.y ||
        containerRect.y > containerRect.y + containerRect.height
      ) {
        this.$message.error("请把节点拖入到画布中");
        return;
      }
      left = left - containerRect.x + efContainer.scrollLeft;
      top = top - containerRect.y + efContainer.scrollTop;
      // 居中
      left -= 85;
      top -= 16;
      var nodeId = this.getUUID();

      // 动态生成名字 重名加后缀
      //var origName = nodeMenu.name;
      //var nodeName = origName;
      //var index = 2
      // while (index < 10000) {
      //     var repeat = false
      //     for (var i = 0; i < this.data.nodeList.length; i++) {
      //         let node = this.data.nodeList[i]
      //         if (node.name === nodeName) {
      //             nodeName = origName + index
      //             repeat = true
      //         }
      //     }
      //     if (repeat) {
      //         index++
      //         continue
      //     }
      //     break
      // }
      var node = {
        id: nodeId,
        name: nodeMenu.name,
        type: nodeMenu.type,
        left: left + "px",
        top: top + "px"
        // state: 'success'
      };

      /**
       * 这里可以进行业务判断、是否能够添加该节点
       */
      //  开始节点只允许添加一个
      if (node.type === "start") {
        for (var i = 0; i < this.data.nodeList.length; i++) {
          let element = this.data.nodeList[i];
          if (element.type === "start") {
            this.$message.error("该节点只允许有一个！");
            return;
          }
        }
      }
      //  结束节点只允许添加一个
      if (node.type === "end") {
        for (var i = 0; i < this.data.nodeList.length; i++) {
          let element = this.data.nodeList[i];
          if (element.type === "end") {
            this.$message.error("该节点只允许有一个！");
            return;
          }
        }
      }

      this.data.nodeList.push(node);
      this.$nextTick(function() {
        this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions);
        this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions);
        this.jsPlumb.draggable(nodeId, {
          containment: "parent"
        });
      });
      this.activeElement.type = "node";
      this.activeElement.nodeId = nodeId;
      //this.$refs.nodeForm.nodeInit(this.data, nodeId)
    },
    /**
     * 删除节点
     * @param nodeId 被删除节点的ID
     */
    deleteNode(nodeId, nodeName) {
      this.$confirm('确定要删除节点"' + nodeName + '"?', "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
        closeOnClickModal: false
      })
        .then(() => {
          /**
           * 这里需要进行业务判断，是否可以删除
           */
          this.data.nodeList = this.data.nodeList.filter(function(node) {
            if (node.id === nodeId) {
              // 伪删除，将节点隐藏，否则会导致位置错位
              // node.show = false
              return false;
            }
            return true;
          });
          this.$nextTick(function() {
            this.jsPlumb.removeAllEndpoints(nodeId);
          });
        })
        .catch(() => {});
      return true;
    },

    clickNode(nodeId) {
      this.activeElement.type = "node";
      this.activeElement.nodeId = nodeId;
      // this.$refs.nodeForm.nodeInit(this.data, nodeId)
      console.log("点击了node");
    },

    // deleteNode(nodeId) {
    //     this.activeElement.type = 'node'
    //     this.activeElement.nodeId = nodeId
    //     this.$refs.nodeForm.nodeInit(this.data, nodeId)
    //     console.log(nodeId);
    //     this.$confirm('确定要删除节点' + nodeId + '?', '提示', {
    //         confirmButtonText: '确定',
    //         cancelButtonText: '取消',
    //         type: 'warning',
    //         closeOnClickModal: false
    //     }).then(() => {
    //         /**
    //          * 这里需要进行业务判断，是否可以删除
    //          */
    //         this.data.nodeList = this.data.nodeList.filter(function (node) {
    //             if (node.id === nodeId) {
    //                 // 伪删除，将节点隐藏，否则会导致位置错位
    //                 // node.show = false
    //                 return false
    //             }
    //             return true
    //         })
    //         this.$nextTick(function () {
    //             this.jsPlumb.removeAllEndpoints(nodeId);
    //         })
    //     }).catch(() => {
    //     })
    //     //this.deleteNode(this.activeElement.nodeId);
    // },

    // 是否具有该线
    hasLine(from, to) {
      for (var i = 0; i < this.data.lineList.length; i++) {
        var line = this.data.lineList[i];
        if (line.from === from && line.to === to) {
          return true;
        }
      }
      return false;
    },
    // 是否含有相反的线
    hashOppositeLine(from, to) {
      return this.hasLine(to, from);
    },

    nodeRightMenu(nodeId, evt) {
      this.menu.show = true;
      this.menu.curNodeId = nodeId;
      this.menu.left = evt.x + "px";
      this.menu.top = evt.y + "px";
    },
    // 流程数据信息
    dataInfo() {
      this.flowInfoVisible = true;
      this.$nextTick(function() {
        this.$refs.flowInfo.init();
      });
    },
    // 加载流程图    //;;'l'l;
    dataReload(data) {
      this.easyFlowVisible = false;
      this.data.nodeList = [];
      this.data.lineList = [];
      this.$nextTick(() => {
        data = lodash.cloneDeep(data);
        this.easyFlowVisible = true;
        this.data = data;
        this.$nextTick(() => {
          this.jsPlumb = jsPlumb.getInstance();
          this.$nextTick(() => {
            this.jsPlumbInit();
          });
        });
      });
    },

    // 模拟载入数据
    dataReloadC() {
      this.dataReload(getDataC());
    },

    // 下载数据
    downloadData() {
      var conn = this.jsPlumb.getConnections({
        source: "nodeA",
        target: "nodeB"
      })[0];
      // console.log(conn)
      this.$confirm("确定要下载该流程数据吗？", "提示", {
        confirmButtonText: "确定",
        cancelButtonText: "取消",
        type: "warning",
        closeOnClickModal: false
      })
        .then(() => {
          var datastr =
            "data:text/json;charset=utf-8," +
            encodeURIComponent(JSON.stringify(this.data, null, "\t"));
          var downloadAnchorNode = document.createElement("a");
          downloadAnchorNode.setAttribute("href", datastr);
          downloadAnchorNode.setAttribute("download", "data.json");
          downloadAnchorNode.click();
          downloadAnchorNode.remove();
          this.$message.success("正在下载中,请稍后...");
        })
        .catch(() => {});
    }
  }
};
</script>